import { CharStreams, CommonTokenStream } from "antlr4ts";
import * as fs from "fs";
import { ParseTreeWalker } from 'antlr4ts/tree/ParseTreeWalker'
import { ParseTreeListener } from "antlr4ts/tree/ParseTreeListener";
import { ExtAttrName, NodeBaseAttrName, NodeType, SceneAttrName, SignalAttrName, SubAttrName, Tscn, TscnAttr, TscnExtResource, TscnNode, TscnProp, TscnSignal, TscnSubResource } from "../../model/tscn";
import { Gen } from "../../model/gen";
import { gdxLexer } from "./gdxLexer";
import { ArrayContext, AttrContext, BlockContext, DictionaryContext, EndBlockContext, FileContext, gdxParser, InstanceContext, NodeContext, OpenBlockContext, PairContext, ValueContext } from "./gdxParser";
import { gdxListener } from "./gdxListener";
import { GdxAttrName, GdxNodeType } from "../../model/gdx";

class MyListener implements gdxListener {
    scene: Tscn
    currentNode: TscnNode | TscnSubResource | null = null;
    lastParent: TscnNode | null = null;
    nameToNoe: Map<string, TscnNode> = new Map()
    tscn: string;

    constructor(fileName: string) {
        this.scene = new Tscn();
        this.tscn = "";
    }

    /**
     * Exit a parse tree produced by `gdxParser.file`.
     * @param ctx the parse tree
     */
    exitFile(ctx: FileContext) {
        // console.log("scn", this.scene)
    }

    /**
     * Enter a parse tree produced by `gdxParser.node`.
     * @param ctx the parse tree
     */
    enterNode(ctx: NodeContext) {
        let nodeType = ctx.openBlock().ID().text;
        switch (nodeType) {
            case GdxNodeType.SCENE:
                // scene
                this.parseScene(ctx.openBlock())
                break
            case GdxNodeType.EXT:
                // ext
                this.parseExt(ctx.openBlock())
                break
            case GdxNodeType.SUB:
                // sub
                this.parseSub(ctx.openBlock(), ctx.block())
                break
            case GdxNodeType.SIGNAL:
                // signal
                this.parseSignal(ctx.openBlock())
                break
            case GdxNodeType.PROP:
                this._parseProp(ctx.openBlock(), ctx.block())
                break;
            case GdxNodeType.PROPS:
            case GdxNodeType.CHILDREN:
                break;
            default:
                // node
                this.parseNode(ctx.openBlock(), ctx.block())
        }
    }

    parseScene(opt: OpenBlockContext) {
        // 属性获取
        opt.attr().forEach(a => {
            if (a.ID().text == SceneAttrName.STEPS) {
                this.scene.steps = this._parseInt(a.STRING_().text)
            } else if (a.ID().text == SceneAttrName.FORMAT) {
                this.scene.fromat = this._parseInt(a.STRING_().text)
            }
        })
    }
    parseExt(opt: OpenBlockContext) {
        let path = "";
        let type = "";
        let id = 0
        // 属性获取
        opt.attr().forEach(a => {
            if (a.ID().text == ExtAttrName.PATH) {
                path = a.STRING_().text.replace(/"/g, '')
            } else if (a.ID().text == ExtAttrName.TYPE) {
                type = a.STRING_().text.replace(/"/g, '')
            } else if (a.ID().text == ExtAttrName.ID) {
                id = this._parseInt(a.STRING_().text)
            }
        })
        this.scene.pushExt(new TscnExtResource(
            path,
            type,
            id
        ))
    }
    parseSignal(opt: OpenBlockContext) {

        let signal = "";
        let from = "";
        let to = "";
        let method = "";

        // 属性获取
        opt.attr().forEach(a => {
            if (a.ID().text == SignalAttrName.SIGNAL) {
                signal = a.STRING_().text.replace(/"/g, '')
            } else if (a.ID().text == SignalAttrName.FROM) {
                from = a.STRING_().text.replace(/"/g, '')
            } else if (a.ID().text == SignalAttrName.TO) {
                to = a.STRING_().text.replace(/"/g, '')
            } else if (a.ID().text == SignalAttrName.METHOD) {
                method = a.STRING_().text.replace(/"/g, '')
            }
        })

        this.scene.pushSignal(new TscnSignal(
            signal, from, to, method
        ))
    }
    parseSub(opt: OpenBlockContext, block: BlockContext[]) {
        // Sub Attr
        let type = "";
        let id = 0
        // 属性获取
        opt.attr().forEach(a => {
            if (a.ID().text == ExtAttrName.TYPE) {
                type = a.STRING_().text.replace(/"/g, '')
            } else if (a.ID().text == ExtAttrName.ID) {
                id = this._parseInt(a.STRING_().text)
            }
        })
        this.currentNode = new TscnSubResource(type, id)
        this.scene.pushSub(this.currentNode)
        // Sub Prop
    }
    parseNode(opt: OpenBlockContext, block: BlockContext[]) {

        // Node Attr
        let type = opt.ID().text;
        let name = "";
        let parent = "-1";
        let hasOtherAttr: AttrContext[] = [];

        // 属性获取
        opt.attr().forEach(a => {
            if (a.ID().text == NodeBaseAttrName.NAME) {
                name = a.STRING_().text.replace(/"/g, '')
            } else if (a.ID().text == NodeBaseAttrName.PARENT) {
                parent = a.STRING_().text.replace(/"/g, '')
            } else {
                hasOtherAttr.push(a);
            }
        })
        // console.log("nodeBaseAttr",name,type,parent)
        this.currentNode = new TscnNode(
            name,
            type,
            parent
        )
        this.nameToNoe.set(name, this.currentNode)
        if (parent == "-1") {
            this.lastParent = this.currentNode;
            this.scene.pushRootNode(this.currentNode)
        } else if (parent == '.') {
            this.lastParent?.children.push(
                this.currentNode
            )
        } else if (parent) {
            let parentNode = this.nameToNoe.get(parent);
            if (parent == null) {
                console.log("node", name, parent)
                throw new Error("父节点查找失败")
            }
            parentNode?.children.push(this.currentNode)
        }
        hasOtherAttr.forEach(a => {
            (this.currentNode as TscnNode).pushAttr(
                new TscnAttr(
                    a.ID().text,
                    "string",
                    a.STRING_().text.replace(/"/g, ''))
            )
        })
        // Node Prop
        // Node Children
    }
    private _parseProp(opt: OpenBlockContext, block: BlockContext[]) {
        let propName = "";
        opt.attr().forEach(a => {
            if (a.ID().text == GdxAttrName.NAME) {
                propName = a.STRING_().text.replace(/"/g, '')
            }
        })
        block.forEach(b => {
            this._parsePropValue(propName, b);
        })
    }
    private _parseInt(s: string) {
        return parseInt(s.replace(/"/g, ''));
    }

    private _parsePropValue(
        propName: string,
        block: BlockContext
    ) {
        if (this.currentNode) {
            if (block.node()) {
                throw new Error("prop value=node error")
            } else if (block.value()) {
                let value = this.enterValue(block.value() as ValueContext)
                // console.log("value",propName, value)
                this.currentNode.pushProp(
                    new TscnProp(propName, value["type"], value["value"])
                )
            }
        }
    }

    /**
     * Enter a parse tree produced by `tscnParser.value`.
     * @param ctx the parse tree
     */
    enterValue(ctx: ValueContext) {
        let result = {
            type: "",
            value: ""
        };
        if (ctx.INT()) {
            result = {
                type: "int",
                value: ctx.INT()?.toString() as string
            }
        } else if (ctx.FLOAT_()) {
            result = {
                type: "float",
                value: ctx.FLOAT_()?.toString() as string
            }
        } else if (ctx.STRING_()) {
            result = {
                type: "string",
                value: ctx.STRING_()?.toString().replace(/"/g, "*") as string
            }
        } else if (ctx.instance()) {
            result = {
                type: "instance",
                value: ctx.instance()?.text as string
            }
        } else if (ctx.array()) {
            result = {
                type: "array",
                value: this.enterArray(ctx.array() as ArrayContext) as string
            }
        } else if (ctx.dictionary()) {
            result = {
                type: "dictionary",
                value: this.enterDictionary(ctx.dictionary() as DictionaryContext) as string
            }
        } else {
            result = {
                type: "simple",
                value: ctx.text
            }
        }
        return result;
    }
    /**
     * Enter a parse tree produced by `tscnParser.array`.
     * @param ctx the parse tree
     */
    enterArray(ctx: ArrayContext) {
        let valueArray: string[] = [];
        let vCtx = ctx.value();
        vCtx.forEach(v => {
            valueArray.push(this.enterValue(v)["value"])
        })
        return `[${valueArray.join(",")}]`;
    }


    /**
     * Enter a parse tree produced by `tscnParser.dictionary`.
     * @param ctx the parse tree
     */
    enterDictionary(ctx: DictionaryContext) {
        let valueArray: string[] = [];
        let pairCtx = ctx.pair();
        pairCtx.forEach(p => {
            valueArray.push(this.enterPair(p))
        })
        return `{ ${valueArray.join(",")} }`;
    }
    /**
     * Enter a parse tree produced by `tscnParser.pair`.
     * @param ctx the parse tree
     */
    enterPair(ctx: PairContext) {
        let key = ctx.STRING_()
            ? ctx.STRING_()?.text
            : ctx.ID()?.text;
        let value = this.enterValue(ctx.value())["value"]

        return `${key} : ${value}`
    }

}


function parseGdx(fileName: string) {
    let code = fs.readFileSync(fileName, "utf-8");
    let inputStream = CharStreams.fromString(code);
    let lexer = new gdxLexer(inputStream);
    let tokenStream = new CommonTokenStream(lexer);
    let parser = new gdxParser(tokenStream);
    let tree = parser.file();

    // console.log("tree", tree.toStringTree())
    const listener = new MyListener(fileName);
    ParseTreeWalker.DEFAULT.walk(listener as ParseTreeListener, tree)

    fs.writeFileSync(
        fileName.replace(".xml", "1.json"),
        JSON.stringify(listener.scene, null, 2)
    )
    console.log("json ok", fileName)

    console.log("gdx ok", fileName);
}

let fileName = "../../test/Main.xml"
parseGdx(fileName);

function t() {
    let fileNames = [
        "../../test/Main.xml",
        "../../test/HUD.xml",
        "../../test/Mob.xml",
        "../../test/Player.xml",
    ]
    fileNames.forEach(name => {
        parseGdx(name)
    })
}

// t();

export {
    parseGdx
}